package com.qf.shiro2204.config;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.qf.shiro2204.user.entity.User;
import com.qf.shiro2204.user.service.UserService;
import com.qf.shiro2204.utils.ApplicationContextUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.Arrays;
import java.util.List;

/**
 * 使用shiro时需要提供一个自定义Realm对象，用于向shiro提供 身份信息和权限信息
 */
public class CustomRealm extends AuthorizingRealm {

    @Autowired
    UserService userService;

    /**
     * 该方法用于获取当前用户的 角色和权限相关信息
     * @param principals
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {

        //1. 获取当前用户的身份信息
        // 注意：这里返回的对象取决于 doGetAuthenticationInfo方法返回值的第一个参数
        User user = (User) principals.getPrimaryPrincipal();

        //2. 根据user对象中的信息，去数据库中查询 该用户的 角色 和权限信息
        List<String> roles = getRolesFromDB(user);
        List<String> permissions = getPermissionsFromDB(user);

        //3. 把查询到的角色和权限信息返回给shiro，由shiro去做后续的判断处理
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        simpleAuthorizationInfo.addRoles(roles);  // 设置当前用户的角色信息

        simpleAuthorizationInfo.addStringPermissions(permissions); // 设置当前用户的权限信息

        return simpleAuthorizationInfo;
    }

    private List<String> getPermissionsFromDB(User user) {

//        List<String> list = Arrays.asList("order:save:*");
//        List<String> permsList =  userService.getPerms(user);
        //1. 访问缓存
        List<String> perms = user.getPerms();

        if (perms!=null){
            return perms;
        }
        //2. 访问db
        List<String> permsList = userService.getPermsByMpAPI(user);

        //3. 写入缓存
        user.setPerms(permsList);

        return permsList;
    }


    private List<String> getRolesFromDB(User user) {

//        List<String> roleList = Arrays.asList("admin", "user");

//        List<String> roleList = userService.getRoles(user);

        // 1. 检查是否已经缓存过当前登录用户的角色信息 ，如果有直接使用缓存数据
        List<String> roles = user.getRoles();
        if (roles!=null){
            return roles;
        }

        // 2. 如果缓存未命中，则查询数据库
        List<String> roleList = userService.getRolesByMpAPI(user);

        // 3. 缓存角色信息到user对象中
        user.setRoles(roleList);

        return roleList;
    }


    /**
     * 该方法提供用户的身份认证信息
     * 包含用户的密码
     * @param token
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

        UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;
        // 获取当前请求中页面传递过来的用户名
        String username = usernamePasswordToken.getUsername();

        // 根据 token 对象中的用户名，查询该用户对应的 信息（一定要包含该用户名的真正的密码）
        User user = getUserFromDB(username);


        // 判断user是否为null
        if (user==null){
            // 用户名不存在
            throw new RuntimeException("用户名不存在");
        }
        // 注意：校验密码的过程，不需要我们自己写代码了，只要把该用户数据库中的密码传给shiro即可，shiro后续的流程中会校验


        // 需要我们返回 AuthenticationInfo 对象即可
        /**
         * 构造函数中的参数
         * 参数1： 代表当前用户的身份信息的对象，可以放入user对象也可以放入用户名。这里放入的对象，可以通过subject对象进行获取
         * 参数2： 数据库中的密码
         * 参数3： 盐 ，注意类型需要与shiro的参数类型匹配，固定写法 ByteSource.Util.bytes(user.getSalt())
         * 参数4： 当前realm的名字，固定写法
         */
        SimpleAuthenticationInfo simpleAuthenticationInfo
                = new SimpleAuthenticationInfo(user,user.getPassword()
                ,ByteSource.Util.bytes(user.getSalt())
                ,getName());
        return simpleAuthenticationInfo;
    }

    /**
     * 从数据库中查询用户信息
     * @param username
     * @return
     */
    private User getUserFromDB(String username) {

//        UserService userService = ApplicationContextUtils.getBean(UserService.class);
        UserService userService = (UserService) ApplicationContextUtils.getBean("userServiceImpl");

        User dbuser = userService.getOne(new QueryWrapper<User>().eq("username", username));

        return dbuser;
    }
}
